import java.awt.*;
import java.awt.image.*;
import javax.swing.*;
//import javax.swing.text.html.HTMLDocument.HTMLReader.SpecialAction;

 

class RayTracer extends JFrame // create frame for canvas
{
    /**
	 * 
	 */
	private static final long serialVersionUID = 1L;
	static final int WIDTH = 1024;
    static final int HEIGHT = 1024;
    static final int MAX_REFLECT = 25;
    
    private GCanvas m_canvas;
    private GeometryUnion m_scene;
    private PerspectiveCamera m_camera;

    public RayTracer() // constructor
    {
        super("Graphics Demo");
        setBounds(50,50,WIDTH, HEIGHT);// set frame
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        Container con=this.getContentPane(); // inherit main frame
        con.setBackground(Color.blue);        // paint background
        m_canvas=new GCanvas(WIDTH, HEIGHT);     // create drawing canvas
        con.add(m_canvas); setVisible(true);// add to frame and show
        Graphics g = m_canvas.getGraphics();
        
        setupEnvironment();
        rayTraceReflection();
        m_canvas.paint(g);
    }
    
    public void setupEnvironment()
    {
    	Plane plane = new Plane(new Vector3(0, 1, 0), 0);
    	Sphere sphere1 = new Sphere(new Vector3(-10.0, 10.0, -10.0), 10.0);
    	Sphere sphere2 = new Sphere(new Vector3(10.0, 10.0, -10.0), 10.0);
    	Geometry[] geometries = {plane, sphere1, sphere2};
    	m_scene = new GeometryUnion(geometries);
    	
    	plane.material = new CheckerMaterial(0.1, 0.5);
    	sphere1.material = new PhongMaterial(Color3.red(), Color3.white(), 32.0, 0.25);
    	sphere2.material = new PhongMaterial(Color3.blue(), Color3.white(), 32.0, 0.25);
    	
    	m_camera = new PerspectiveCamera(new Vector3(0.0, 5.0, 15.0), new Vector3(0.0, 0.0, -1.0), new Vector3(0.0, 1.0, 0.0), 90.0);
    }
    
    public void rayTraceReflection()
    {
    	int[] pixels = m_canvas.getPixels();
    	int height = m_canvas.getHeight();
    	int width = m_canvas.getWidth();
    	
    	for(int y = 0; y < height; ++y)
    	{
    		double sy = 1.0 - (double)y / (double)height; 
         	int start = y * width;
         	
    		for(int x = 0; x < width; ++x)
         	{
    			double sx = (double)x / (double)width;
         		Ray3 ray = m_camera.generateRay(sx, sy);
         		Color3 color = rayTraceRecursive(ray, MAX_REFLECT);
         		
         		int red = Math.min((int)(color.x * 255.0), 255) << 16;
     			int green = Math.min((int)(color.y * 255.0), 255)  << 8;
     			int blue = Math.min((int)(color.z * 255.0), 255);
     			int alpha = 255 << 24;
     			pixels[start + x] = alpha | red | green | blue;
         	}
    	}
    }
    
    private Color3 rayTraceRecursive(Ray3 ray, double maxReflect)
    {
    	IntersectResult result = m_scene.intersect(ray);
    	
    	if(result.geometry != null)
    	{
    		double reflectiveness = result.geometry.material.reflectiveness;
    		Color3 color = result.geometry.material.sample(ray, result.position, result.normal);
    		color.mul(1 - reflectiveness);
    		
    		if(reflectiveness > 0 && maxReflect > 0)
    		{
    			Vector3 r = result.normal.mul(-2.0 * result.normal.dot(ray.direction)).add(ray.direction);
    			ray = new Ray3(result.position, r);
    			Color3 relfectedColor = rayTraceRecursive(ray, maxReflect - 1);
    			color = color.add(relfectedColor.mul(reflectiveness));
    		}
    		
    		return color;
    	}
    	else
    	{
    		return Color3.black();
    	}
    }
    
    public void rayTrace()
    {
    	int[] pixels = m_canvas.getPixels();
    	int height = m_canvas.getHeight();
    	int width = m_canvas.getWidth();
    	
    	 for(int y = 0; y < height; ++y)
         {
         	double sy = 1.0 - (double)y / (double)height; 
         	int start = y * width;
         	for(int x = 0; x < width; ++x)
         	{
         		double sx = (double)x / (double)width;
         		Ray3 ray = m_camera.generateRay(sx, sy);
         		IntersectResult result = m_scene.intersect(ray);
         		
         		if(result.geometry != null)
         		{
         			//int depth = 255 - Math.min( (int)((result.distance / MAX_DEPTH) * 255.0), 255);
         			Color3 color = result.geometry.material.sample(ray, result.position, result.normal);
         			int red = Math.min((int)(color.x * 255.0), 255) << 16;
         			int green = Math.min((int)(color.y * 255.0), 255)  << 8;
         			int blue = Math.min((int)(color.z * 255.0), 255);
         			int alpha = 255 << 24;
         			//System.out.println("" + (red >> 16) + " " + (green >> 8) + " " + blue);
         			pixels[start + x] = alpha | red | green | blue;
         		}
         		else
         		{
         			pixels[start + x] |= 255 << 24;
         			//System.out.println("No intersection " + m_pixels[start + x]);
         		}
         	}       	
         } 
    }

    public static void main(String arg[])
    {
    	
        
    	RayTracer rayTracer = new RayTracer();
        
        //Tester tester = new Tester();
        //tester.test();
    }
}

class GCanvas extends Canvas
{
    /**
	 * 
	 */
	private static final long serialVersionUID = 1L;
	private BufferedImage m_image;
    private int[] m_pixels;

    public GCanvas(int width, int height)
    {
        m_image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
        DataBufferInt dataBuffer = (DataBufferInt)(m_image.getRaster().getDataBuffer());
        m_pixels = dataBuffer.getData();
    }
    
    public int[] getPixels()
    {
    	return m_pixels;
    }

    public void paint(Graphics g)
    {
    	Graphics2D g2D=(Graphics2D) g; // cast to 2D
        g2D.drawImage(m_image, 0, 0, this);   
    }
}

